//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.
// 

#include "Ethernet.h"

Define_Module(Ethernet);

void Ethernet :: initialize()
{
    string tempMAC(par("macAddress").stringValue());
    string tempIP(par("IPAddress").stringValue());
    myMacAddress = tempMAC;
    myIPAddress = tempIP;
    txSwitchChannel = gate("toSwitch")->getTransmissionChannel();
    txQueue = new cQueue("transmissionQueue");
    agingTime = par("ageingTime");

    // enum types
    IPv4_type = 800;
    ARP_type  = 806;
    ARP_request  = 807;
    ARP_reply  = 808;
    IP_row = 0;
    MAC_row = 1;

    //arp table initialization
    for(int j=0;j<2;j++)
        for(int i=0; i<4 ; i++)
            arpTable[i][j] = "";

    arpTable[0][0].assign("172.168.32.1");
    arpTable[1][0].assign("‫‪172.168.32.2");
    arpTable[2][0].assign("‫‪172.168.32.3");
    arpTable[3][0].assign("‫‪172.168.32.4");

}

void Ethernet :: handleMessage(cMessage *msg)
{
    if(dynamic_cast<Eth_pck *>(msg))
    {
        Eth_pck *packet = check_and_cast<Eth_pck *>(msg);
        handleEthMessage(packet);
    }
    else if(dynamic_cast<IP_pck *>(msg))
    {
        IP_pck *packet = check_and_cast<IP_pck *>(msg);
        handleIPMessage(packet);
    }
    else if(dynamic_cast<Ageing *>(msg))
        handleAgeingMessage(check_and_cast<Ageing *>(msg));
    else
        drop(msg);
}

void Ethernet :: handleARPMessage(ARP *msg)
{
    if(msg->getArpType() == ARP_request)
    {
        string destIP (msg->getDestIP());
        if(destIP.compare(destIP.size()-12,12, myIPAddress,myIPAddress.size()-12,12) == 0)
        {
            ARP *reply = msg->dup();
            reply->setName("ARP_reply");
            reply->setArpType(ARP_reply);
            reply->setDestMac(myMacAddress.data());

            Eth_pck *outgoingMsg = new Eth_pck("ARP");
            outgoingMsg->setByteLength(14);
            outgoingMsg->setMAC_dest(reply->getSourceMac());
            outgoingMsg->setMAC_source(myMacAddress.data());
            outgoingMsg->setEthertype(ARP_type);
            outgoingMsg->encapsulate(reply);

            forwardToSwitch(outgoingMsg);
        }
    }
    else if(msg->getArpType() == ARP_reply)
    {
        string destIP (msg->getDestIP());
        int index = ipEntry(destIP);
        arpTable[index][1] = msg->getDestMac();

        Ageing *timeleft = new Ageing("ageing_ethernet");
        timeleft->setTableIndex(index);
        scheduleAt(simTime()+ agingTime, timeleft);

        if(txQueue->empty()==false)
        {
            IP_pck *delayedMsg = (IP_pck *)txQueue->pop();
            handleIPMessage(delayedMsg);
        }
    }
    else
        drop(msg);
}

void Ethernet :: handleEthMessage(Eth_pck *msg)
{
    if(msg->isSelfMessage())
        forwardToSwitch(msg);
    else if(msg->getEthertype()==IPv4_type)
    {
        string dest(msg->getMAC_dest());
        if(dest.size() > 16 && dest.compare(dest.size()-17,17, myMacAddress,myMacAddress.size()-17,17) == 0)
        {//IP intended for this node
            IP_pck *decapsulated = (IP_pck*)(msg->decapsulate());
            forwardToIpGate(decapsulated);
        }
        else
            drop(msg);
    }
    else if(msg->getEthertype()==ARP_type)
    {
        ARP *decapsulated = (ARP *)(msg->decapsulate());
        handleARPMessage(decapsulated);
    }
    else
        drop(msg);
}

void Ethernet :: handleIPMessage(IP_pck *msg)
{
    string destIP = msg->getDestAddress();
    int index = ipEntry(destIP);
    if(macExists(index))                              //MAC was in the arp
    {
        string destMac (arpTable[index][1]);
        Eth_pck *outgoingMsg = new Eth_pck("IP");
        outgoingMsg->setByteLength(14);
        outgoingMsg->setMAC_dest(destMac.c_str());
        outgoingMsg->setMAC_source(myMacAddress.c_str());
        outgoingMsg->setEthertype(IPv4_type);
        outgoingMsg->encapsulate(msg);

        forwardToSwitch(outgoingMsg);
    }
    else
        deployARPRequest(msg);
}

void Ethernet :: deployARPRequest(IP_pck *msg)
{
    ARP *arp_request = new ARP("ARP Request");
    arp_request->setArpType(ARP_request);
    arp_request->setSourceIP(myIPAddress.data());
    arp_request->setSourceMac(myMacAddress.data());
    arp_request->setDestIP(msg->getDestAddress());

    txQueue->insert(msg);

    Eth_pck *outgoingMsg = new Eth_pck("ARP");
    outgoingMsg->setByteLength(14);
    outgoingMsg->setMAC_dest("FF:FF:FF:FF:FF:FF");
    outgoingMsg->setMAC_source(myMacAddress.data());
    outgoingMsg->setEthertype(ARP_type);
    outgoingMsg->encapsulate(arp_request);

    forwardToSwitch(outgoingMsg);
}

void Ethernet :: handleAgeingMessage(Ageing *msg)
{
    int index = msg->getTableIndex();
    arpTable[index][1]="";
}

void Ethernet :: forwardToSwitch(Eth_pck *msg)
{
    simtime_t txFinishTime = txSwitchChannel->getTransmissionFinishTime();
    if(txFinishTime <= simTime())
    {
        EV<<msg->getName()<<" sent from "<<getParentModule()->getFullName()<<endl;
        send(msg,"toSwitch");
    }
    else
        scheduleAt(txFinishTime, msg);
}

void Ethernet :: forwardToIpGate(cMessage *msg) { send(msg,"toIP"); }

int Ethernet :: ipEntry(string address)
{
    int tableIndex = -1;
    for(int i=0; i<4 ; i++)
    {
        if(arpTable[i][0].compare(arpTable[i][0].size()-12,12, address,address.size()-12,12) == 0)
        {
            tableIndex = i;
            break;
        }
    }
    return tableIndex;
}

bool Ethernet :: macExists(int index)
{
    string empty ("");
    if(arpTable[index][1].compare(empty) != 0)
        return true;
    return false;
}
